// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: junglekim

#pragma once

#include <memory>
#include <regex>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <time.h>

#include "coin2/exchange/base/feed/timer/deadline_timer.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "coin2/strategy/order_executor.pb.h"
#include "coin2/strategy/order_executor/executor.h"
#include "coin2/strategy/order_executor/layering_executor3.h"
#include "coin2/strategy/order_executor/mm_executor.h"
#include "coin2/strategy/vmm/vmm_dh_util.h"
#include "coin2/strategy/vmm/vmm_pricer.h"
#include "coin2/strategy/vmm/vmm_util.h"

using coin::proto::OrderDuration;
using coin::proto::OrderEvent;
using coin::proto::OrderSide;
using coin::proto::OrderType;
using coin::proto::TradeSide;
using coin2::app::impl::Driver;
using coin2::exchange::base::feed::FeedTimer;
using coin2::exchange::base::feed::timer::DeadlineTimer;
using coin2::exchange::base::order::IOrderSubsystem;
using coin2::exchange::base::order::OrderSpec;
using coin2::exchange::base::order::OrderUpdate;
using coin2::exchange::base::symbology::IProduct;
using coin2::exchange::base::symbology::IProductInfo;
using coin2::exchange::base::symbology::ProductEncyclopedia;
using coin2::strategy::PassiveExecutorConfig;
using coin2::strategy::order_executor::LayeringExecutor3;
using coin2::strategy::order_executor::PassiveOrderExecutor;
using google::protobuf::util::JsonStringToMessage;

namespace coin2::strategy::vmm {

class VmmOrderController {
 public:
  VmmOrderController(const ::nlohmann::json& oc_config, const bool& dry_run, const bool& verbose)
      : configs_(oc_config), dry_run_(dry_run), verbose_(verbose) {}
  virtual void onPricerUpdate(
      const std::unordered_map<std::string, VmmPriceResult>& pricer_results,
      const int64_t& timestamp) = 0;
  virtual void onHeartbeatFeed(const FeedTimer& timer) {}
  virtual void onAccountInfo(const OrderUpdate& upd) = 0;
  virtual void onAccountOrder(const OrderUpdate& upd) = 0;

  void onCleanupSpec(std::string symbol, int64_t timestamp) {
    executors_[symbol]->ManageOrders(timestamp);
    executors_[symbol]->ForceCancelAllWorkingOrders();
  }

  virtual void onCleanupAux(Driver* driver, int num_retries) {
    int num_orders = 0;
    auto ts = GetCurrentTimestamp();
    for (auto& [symbol, executor] : executors_) {
      if (executor) {
        executor->SetEnableOrderSubmission(false);
        num_orders += executor->TryCancelAllWorkingOrders(ts);
      }
    }

    if (num_orders == 0) {
      SPDLOG_INFO("Cleared all pending orders.");
      driver->PostTask([driver]() { driver->Exit(); });
    } else if (num_retries > 10) {
      // LOG(WARNING) << "There may be some pendings orders left open.";
      // Copilot: Make above logging into SPDLOG_WARNING
      SPDLOG_WARN("There may be some pendings orders left open.");

      driver->PostTask([driver]() { driver->Exit(); });
    } else {
      driver->PostDelayedTask(
          [this, driver, num_retries]() { this->onCleanupAux(driver, num_retries + 1); },
          500'000'000LL);
    }
  }

  void PlaceOrder(
      std::string symbol,
      int64_t timestamp,
      OrderSide order_side,
      double price,
      double qty) {
    OrderSpec order_spec(
        *CreateProductFromUniqueString(symbol, timestamp),
        OrderType::LIMIT_ORDER,
        order_side,
        OrderDuration::GTC_ORDER,
        price,
        qty,
        timestamp,
        true,
        0,
        symbol);
    driver_->order()
        ->GetUniqueSystem(MarketExchangeApi::FromString(meas_[symbol]))
        ->gateway()
        ->SubmitOrder(order_spec);
  }

  double SpreadOrders(
      std::string symbol,
      int64_t timestamp,
      OrderSide order_side,
      double price,
      double qty,
      int interval,
      int num) {
    const IProductInfo* pi = &(executors_[symbol]->product_info());
    double max_precision = pi->tick_price().max_precision();
    double min_qty = pi->tick_qty().submittable_min_qty();
    double price_i = 0;
    for (int i = num - 1; i >= 0; i--) {
      double qty_i = pi->tick_qty().Round(qty * 2 * (i + 1) / (1 + num) / num);
      if (qty_i > min_qty) {
        if (order_side == OrderSide::BUY_ORDER) {
          price_i = pi->tick_price().RoundDown(price - i * interval * std::pow(10, -max_precision));
        } else {
          price_i = pi->tick_price().RoundUp(price + i * interval * std::pow(10, -max_precision));
        }
        PlaceOrder(symbol, timestamp, order_side, price_i, qty_i);
      }
    }
    return price_i;
  }

  void
  SubmitAggOrder(std::string symbol, int64_t timestamp, double price, double qty, OrderSide side) {
    if (side == OrderSide::BUY_ORDER) {
      executors_[symbol]->ForceFireBuyOrder(timestamp, price, qty, symbol);
    } else {
      executors_[symbol]->ForceFireSellOrder(timestamp, price, qty, symbol);
    }
  }

  const std::unordered_map<std::string, VmmPriceResult> GetPrevResult() const {
    return prev_results_;
  }

  void InitExecutor(
      std::string symbol,
      int64_t start_time,
      PassiveExecutorConfig config,
      IOrderSubsystem* os,
      Driver* driver,
      const std::optional<double>& qty_tolerance_bp,
      const std::optional<double>& lot_size_quote,
      const std::optional<double>& qty_cancel_pct,
      const bool is_randomised_post,
      const int cancel2order_ratio) {
    driver_ = driver;
    auto get_product_holder = [this](const IProduct& product, int64_t timestamp) {
      return driver_->product_holder_manager()->GetProductHolder(product, timestamp);
    };
    if (config.has_filter_tag()) {
      std::smatch match_result;
      std::string afop = "add_from_order_proto";
      if (!std::regex_match(afop, match_result, std::regex(config.filter_tag()))) {
        config.set_accept_foreign_order(true);
      }
    }

    executors_[symbol] = std::make_unique<LayeringExecutor3>(
        config,
        os,
        start_time,
        nullptr,
        get_product_holder,
        qty_tolerance_bp,
        lot_size_quote,
        qty_cancel_pct,
        is_randomised_post,
        cancel2order_ratio,
        verbose_);

    products_[symbol] = CreateProductFromUniqueString(symbol, start_time);
    meas_[symbol] = config.mea();
    os_[symbol] = driver_->order()->GetUniqueSystem(MarketExchangeApi::FromString(meas_[symbol]));
    prev_results_[symbol] = {nullptr,
                             {0.0, 0.0},
                             {0.0, 0.0},
                             {0.0, std::nullopt},
                             {0.0, std::nullopt}};
  }

  std::vector<std::pair<TimerType, std::string>> GetTimerMap() const { return timer_map_; }

  void PassConTimer(std::string symbol, std::unique_ptr<DeadlineTimer> timer) {
    con_timers_[symbol] = std::move(timer);
  }

 protected:
  Driver* driver_;
  ::nlohmann::json configs_;
  bool dry_run_;
  bool verbose_;
  std::unordered_map<std::string, std::unique_ptr<LayeringExecutor3>> executors_;
  std::unordered_map<std::string, std::unique_ptr<IProduct>> products_;
  std::unordered_map<std::string, std::string> meas_;
  std::unordered_map<std::string, VmmPriceResult> prev_results_;
  std::vector<std::pair<TimerType, std::string>> timer_map_;
  std::unordered_map<std::string, std::unique_ptr<DeadlineTimer>> con_timers_;
  std::unordered_map<std::string, IOrderSubsystem*> os_;
};

class VmmBasicOrderController : public VmmOrderController {
 public:
  VmmBasicOrderController(
      const ::nlohmann::json& oc_config,
      const bool& dry_run,
      const bool& verbose)
      : VmmOrderController(oc_config, dry_run, verbose) {
    for (const auto& config : configs_.items()) {
      std::string symbol = config.key();
      last_submit_times_[symbol] = 0;
    }
  }

  void onPricerUpdate(
      const std::unordered_map<std::string, VmmPriceResult>& result,
      const int64_t& timestamp) final {
    for (const auto& [symbol, prices] : result) {
      double sticky_bp = configs_[symbol]["sticky_bp"];
      int64_t min_period = configs_[symbol]["order_update_period_sec"].get<int>() * 1e9;
      int interval = configs_[symbol]["interval"];
      int num = configs_[symbol]["num"];
      double lot_size = configs_[symbol]["lot_size"];
      double min_pos = configs_[symbol]["min_pos"];
      double max_pos = configs_[symbol]["max_pos"];
      double cur_pos = executors_[symbol]->GetPosition();
      double buy_lot_size = std::max(std::min(lot_size, max_pos - cur_pos), 0.0);
      double sell_lot_size = std::max(std::min(lot_size, cur_pos - min_pos), 0.0);

      if (std::abs(
              prices.priced_bid.first.value() / prev_results_[symbol].priced_bid.first.value() -
              1) < sticky_bp * 1e-4 &&
          std::abs(
              prices.priced_ask.first.value() / prev_results_[symbol].priced_ask.first.value() -
              1) < sticky_bp * 1e-4) {
        continue;
      }
      if (!dry_run_ && (timestamp - last_submit_times_[symbol] > min_period)) {
        onCleanupSpec(symbol, timestamp);
        if (driver_->order()
                ->GetUniqueSystem(MarketExchangeApi::FromString(meas_[symbol]))
                ->gateway()
                ->order_manager()
                .GetAllWorkingOrders()
                .empty()) {
          double bidp = SpreadOrders(
              symbol,
              timestamp,
              OrderSide::BUY_ORDER,
              prices.priced_bid.first.value(),
              buy_lot_size,
              interval,
              num);
          double askp = SpreadOrders(
              symbol,
              timestamp,
              OrderSide::SELL_ORDER,
              prices.priced_ask.first.value(),
              sell_lot_size,
              interval,
              num);
          last_submit_times_[symbol] = timestamp;
          prev_results_[symbol].priced_bid.first = bidp;
          prev_results_[symbol].priced_ask.first = askp;
          continue;
        }
      }
    }
  }

  void onAccountInfo(const OrderUpdate& upd) final {
    for (auto& [_, executor] : executors_) {
      executor->onAccountInfo(upd);
    }
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    const auto symbol = GetSymbol(upd.product());
    executors_[symbol]->onAccountOrder(upd);
    auto oe = upd.product_order_info().event();
  }

 protected:
  std::unordered_map<std::string, int64_t> last_submit_times_;
};

class VmmSmoothOrderController : public VmmOrderController {
 public:
  VmmSmoothOrderController(
      const ::nlohmann::json& oc_config,
      const bool& dry_run,
      const bool& verbose)
      : VmmOrderController(oc_config, dry_run, verbose) {
    for (const auto& config : configs_.items()) {
      std::string symbol = config.key();
      prev_results_[symbol].priced_bid.first = 0.0;
      prev_results_[symbol].priced_ask.first = 0.0;
      remaining_[symbol] = {0, 0};
    }
  }

  void onPricerUpdate(
      const std::unordered_map<std::string, VmmPriceResult>& result,
      const int64_t& timestamp) final {
    for (const auto& [symbol, prices] : result) {
      const IProductInfo* pi = &(executors_[symbol]->product_info());
      double sticky_bp = configs_[symbol]["sticky_bp"];
      double buyp = pi->tick_price().RoundDown(prices.priced_bid.first.value());
      double sellp = pi->tick_price().RoundUp(prices.priced_ask.second.value());
      double buyqty = 0;
      double sellqty = 0;
      if (std::abs(
              prices.priced_bid.first.value() / prev_results_[symbol].priced_bid.first.value() -
              1) < sticky_bp * 1e-4 &&
          std::abs(
              prices.priced_ask.first.value() / prev_results_[symbol].priced_ask.first.value() -
              1) < sticky_bp * 1e-4) {
        return;
      }
      if (remaining_[symbol].first <= 0 || remaining_[symbol].second <= 0) {
        double lot_size = configs_[symbol]["lot_size"];
        buyqty = pi->tick_qty().Round(lot_size);
        sellqty = pi->tick_qty().Round(lot_size);
      } else {
        buyqty = pi->tick_qty().Round(remaining_[symbol].first);
        sellqty = pi->tick_qty().Round(remaining_[symbol].second);
      }
      prev_results_[symbol].priced_bid.first = buyp;
      prev_results_[symbol].priced_ask.first = sellp;
      remaining_[symbol] = {buyqty, sellqty};
      if (!dry_run_) {
        executors_[symbol]->ManageMmOrders(timestamp, sellp, buyp, sellqty, buyqty, true, true);
      }
      // LOG(INFO) << timestamp << " symbol " << symbol << " sellp " << sellp << " buyp " << buyp
      // << " sellqty " << sellqty << " buyqty " << buyqty << std::endl;
      return;
    }
  }

  void onAccountInfo(const OrderUpdate& upd) final {
    const auto symbol = GetSymbol(upd.product());
    executors_[symbol]->onAccountInfo(upd);
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    const auto symbol = GetSymbol(upd.product());
    auto oe = upd.product_order_info().event();
    executors_[symbol]->onAccountOrder(upd);
    if (oe.type() == OrderEvent::ORDER_FILLED) {
      if (oe.trade_side() == TradeSide::TRADE_BUY_SIDE) {
        remaining_[symbol].first -= oe.fill_qty();
      } else {
        remaining_[symbol].second -= oe.fill_qty();
      }
    }
  }

 protected:
  std::unordered_map<std::string, std::pair<double, double>> remaining_;
};

class VmmAscendingLayeringOrderController : public VmmOrderController {
 public:
  VmmAscendingLayeringOrderController(
      const ::nlohmann::json& oc_config,
      const bool& dry_run,
      const bool& verbose)
      : VmmOrderController(oc_config, dry_run, verbose) {
    timer_map_.push_back({TimerType::FEED, ""});
    for (const auto& config : configs_.items()) {
      std::string symbol = config.key();
      last_submit_times_[symbol] = 0;
    }
  }

  void onPricerUpdate(
      const std::unordered_map<std::string, VmmPriceResult>& result,
      const int64_t& timestamp) final {
    for (const auto& [symbol, prices] : result) {
      bool randomise = configs_[symbol]["randomise"];
      double bandwidth_bp = configs_[symbol]["bandwidth_bp"];
      int num = configs_[symbol]["num"];
      double lot_size = configs_[symbol]["lot_size"];

      if (dry_run_) continue;
      auto& executor = executors_.at(symbol);
      executor->UpdateState(timestamp);
      executor->ManageRisk(timestamp);

      const IProductInfo* pi = &(executor->product_info());
      executor->ManageSideOrders(
          timestamp,
          1,
          order_executor::LayeringLevels::ConcaveAscendingSize(
              pi,
              num,
              1,
              prices.priced_bid.first.value() / (1 + bandwidth_bp * 1e-4),
              prices.priced_bid.first.value(),
              lot_size,
              randomise));
      executor->ManageSideOrders(
          timestamp,
          -1,
          order_executor::LayeringLevels::ConcaveAscendingSize(
              pi,
              num,
              -1,
              prices.priced_ask.first.value(),
              prices.priced_ask.first.value() * (1 + bandwidth_bp * 1e-4),
              lot_size,
              randomise));
      prev_results_[symbol].priced_bid.first = prices.priced_bid.first.value();
      prev_results_[symbol].priced_ask.first = prices.priced_ask.first.value();
    }
  }

  void onAccountInfo(const OrderUpdate& upd) final {
    for (auto& [_, executor] : executors_) {
      executor->onAccountInfo(upd);
    }
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    const auto symbol = GetSymbol(upd.product());
    executors_[symbol]->onAccountOrder(upd);
  }

 protected:
  std::unordered_map<std::string, int64_t> last_submit_times_;
};

class VmmLayeringOrderController : public VmmOrderController {
 public:
  VmmLayeringOrderController(
      const ::nlohmann::json& oc_config,
      const bool& dry_run,
      const bool& verbose)
      : VmmOrderController(oc_config, dry_run, verbose) {
    timer_map_.push_back({TimerType::FEED, ""});
    for (const auto& config : configs_.items()) {
      std::string symbol = config.key();
      last_submit_times_[symbol] = 0;
    }
  }

  void onPricerUpdate(
      const std::unordered_map<std::string, VmmPriceResult>& result,
      const int64_t& timestamp) final {
    for (const auto& [symbol, prices] : result) {
      double bandwidth_bp = configs_[symbol]["bandwidth_bp"];
      int num = configs_[symbol]["num"];
      double lot_size = configs_[symbol]["lot_size"];

      if (dry_run_) continue;
      auto& executor = executors_.at(symbol);
      executor->UpdateState(timestamp);
      executor->ManageRisk(timestamp);

      const IProductInfo* pi = &(executor->product_info());
      executor->ManageSideOrders(
          timestamp,
          1,
          order_executor::LayeringLevels::EqualSize(
              pi,
              num,
              1,
              prices.priced_bid.first.value() / (1 + bandwidth_bp * 1e-4),
              prices.priced_bid.first.value(),
              lot_size));
      executor->ManageSideOrders(
          timestamp,
          -1,
          order_executor::LayeringLevels::EqualSize(
              pi,
              num,
              -1,
              prices.priced_ask.first.value(),
              prices.priced_ask.first.value() * (1 + bandwidth_bp * 1e-4),
              lot_size));
    }
  }

  void onAccountInfo(const OrderUpdate& upd) final {
    for (auto& [_, executor] : executors_) {
      executor->onAccountInfo(upd);
    }
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    const auto symbol = GetSymbol(upd.product());
    executors_[symbol]->onAccountOrder(upd);
    auto oe = upd.product_order_info().event();
  }

 protected:
  std::unordered_map<std::string, int64_t> last_submit_times_;
};

class VmmApproachOrderController : public VmmOrderController {
 public:
  VmmApproachOrderController(
      const ::nlohmann::json& oc_config,
      const bool& dry_run,
      const bool& verbose)
      : VmmOrderController(oc_config, dry_run, verbose) {
    timer_map_.push_back({TimerType::FEED, ""});
    for (const auto& config : configs_.items()) {
      std::string symbol = config.key();
      done_[symbol] = false;
      ApproachPhase ap;
      prev_phase_[symbol] = ap;
      int phase_num = 1;
      for (auto& phase : config.value()["phases"].items()) {
        ApproachPhase ap;
        ap.buysell = (phase.value()["buysell"].get<std::string>() == "BUY") ? 1 : -1;
        ap.target_pos_change = phase.value()["target_pos_change"].get<double>();
        if (phase.value().contains("datetime_lb")) {
          ap.timestamp0 = Readable2Epoch(phase.value()["datetime_lb"].get<std::string>());
        }
        if (phase.value().contains("datetime_ub")) {
          ap.timestamp1 = Readable2Epoch(phase.value()["datetime_ub"].get<std::string>());
        }
        if (phase.value().contains("price_lb")) {
          ap.price0 = phase.value()["price_lb"].get<double>();
        }
        if (phase.value().contains("price_ub")) {
          ap.price1 = phase.value()["price_ub"].get<double>();
        }
        if (phase.value().contains("min_price")) {
          ap.min_price = phase.value()["min_price"].get<double>();
        }
        if (phase.value().contains("max_price")) {
          ap.max_price = phase.value()["max_price"].get<double>();
        }
        ap.period_min = phase.value()["period_min"].get<double>();
        ap.phase_num = phase_num;
        phases_[symbol][phase_num] = ap;
        phase_num++;
      }
      remaining_[symbol] = 0;
      timer_map_.push_back({TimerType::CONTROLLER, symbol});
    }
  }

  void onPricerUpdate(
      const std::unordered_map<std::string, VmmPriceResult>& result,
      const int64_t& timestamp) final {
    for (const auto& [symbol, prices] : result) {
      const double midp = (prices.priced_bid.first.value() + prices.priced_ask.first.value()) * 0.5;
      double cur_pos = executors_[symbol]->GetPosition();

      ApproachPhase cur_phase;
      int phase_num = 0;
      for (auto& [num, phase] : phases_[symbol]) {
        if (phase.CheckPhase(timestamp, midp)) {
          phase_num = num;
          break;
        }
      }

      if (phase_num == 0) {
        if (prev_phase_[symbol] != cur_phase) {
          onCleanupSpec(symbol, timestamp);
          con_timers_[symbol]->Cancel();
          remaining_[symbol] = 0;
          prev_phase_[symbol] = cur_phase;
        }
        continue;
      }

      cur_phase = phases_[symbol][phase_num];
      if (cur_phase != prev_phase_[symbol]) {
        target_pos_[symbol] = cur_pos + cur_phase.target_pos_change * cur_phase.buysell;
        executors_[symbol]->UpdateMinMaxPosition(
            target_pos_[symbol],
            target_pos_[symbol],
            timestamp);
        prev_phase_[symbol] = cur_phase;
        done_[symbol] = false;
      }

      if (done_[symbol]) {
        SPDLOG_INFO("DONE");
        continue;
      }

      double lot_size = configs_[symbol]["lot_size"];
      auto& executor = executors_[symbol];
      executor->UpdateState(timestamp);
      executor->ManageRisk(timestamp);

      const IProductInfo* pi = &(executor->product_info());
      double min_qty = pi->tick_qty().submittable_min_qty();

      if (remaining_[symbol] <= min_qty) {
        if ((cur_pos + min_qty >= target_pos_[symbol] && cur_phase.buysell == 1) ||
            (cur_pos - min_qty <= target_pos_[symbol] && cur_phase.buysell == -1)) {
          done_[symbol] = true;
          SPDLOG_INFO("DONE");
          continue;
        }

        remaining_[symbol] = (cur_phase.buysell == 1)
                                 ? std::min(lot_size, target_pos_[symbol] - cur_pos)
                                 : std::min(lot_size, cur_pos - target_pos_[symbol]);
        con_timers_[symbol]->ExpiresFromNow(cur_phase.period_min * 60e9);
      }

      prev_results_[symbol].priced_bid.first = prices.priced_bid.first;
      prev_results_[symbol].priced_bid.second = prices.priced_ask.first;
      double buyp = prices.priced_bid.first.value();
      double sellp = prices.priced_ask.first.value();

      if (cur_phase.max_price > 0) {
        buyp = std::min(cur_phase.max_price, prices.priced_bid.first.value());
      }
      if (cur_phase.min_price > 0) {
        sellp = std::max(cur_phase.min_price, prices.priced_ask.first.value());
      }

      executors_[symbol]->UpdateLotSize(remaining_[symbol]);

      if (!dry_run_) {
        executors_[symbol]->ManageMmOrders(timestamp, sellp, buyp, 0, 0, false, false);
      }
    }
  }

  void onHeartbeatFeed(const FeedTimer& timer) {
    std::string symbol = timer_map_[timer.TimerId() - 1].second;
    int64_t timestamp = timer.Timestamp();

    if (done_[symbol]) return;

    if ((prev_phase_[symbol].buysell == 1 &&
         prev_results_[symbol].priced_ask.first.value() > prev_phase_[symbol].max_price) ||
        (prev_phase_[symbol].buysell == -1 &&
         prev_results_[symbol].priced_bid.first.value() < prev_phase_[symbol].min_price)) {
      SPDLOG_INFO("MIN or MAX price unsatisfied, NO aggression this period");
      remaining_[symbol] = 0;
      return;
    }
    onCleanupSpec(symbol, timestamp);

    if (prev_phase_[symbol].buysell == 1) {
      SubmitAggOrder(
          symbol,
          timestamp,
          prev_results_[symbol].priced_ask.first.value(),
          remaining_[symbol],
          OrderSide::BUY_ORDER);
    } else if (prev_phase_[symbol].buysell == -1) {
      SubmitAggOrder(
          symbol,
          timestamp,
          prev_results_[symbol].priced_bid.first.value(),
          remaining_[symbol],
          OrderSide::SELL_ORDER);
    }
    remaining_[symbol] = 0;
  }

  void onAccountInfo(const OrderUpdate& upd) final {
    for (auto& [_, executor] : executors_) {
      executor->onAccountInfo(upd);
    }
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    const auto symbol = GetSymbol(upd.product());
    auto oe = upd.product_order_info().event();
    executors_[symbol]->onAccountOrder(upd);
    if (oe.type() == OrderEvent::ORDER_FILLED) {
      remaining_[symbol] -= oe.fill_qty();
    }
  }

 protected:
  std::unordered_map<std::string, double> target_pos_;
  std::unordered_map<std::string, double> remaining_;
  std::unordered_map<std::string, bool> done_;
  std::unordered_map<std::string, std::unordered_map<int, ApproachPhase>> phases_;
  std::unordered_map<std::string, ApproachPhase> prev_phase_;
};

}  // namespace coin2::strategy::vmm